ci(bench): add per-PR perf canary for extractor/graph/native changes#1488
Conversation
… diff Adds snapshot-pre-bash.sh (PreToolUse Bash) + track-bash-writes.sh (PostToolUse Bash): the pre-hook captures git status --porcelain to a per-worktree temp file before each Bash call; the post-hook diffs the before/after state and appends newly modified or created files to .claude/session-edits.log. This closes the gap where files written by sed -i, printf redirects, tee, heredocs, or build tools (Cargo.lock, lockfiles) were never recorded, causing guard-git.sh to emit false-positive BLOCKED errors. Closes #1457
- clojure.rs: annotate lifetime-anchor assignment to silence false-positive - cfg.rs: remove never-called start_line_of method - complexity.rs: remove never-constructed NotHandled variant; convert irrefutable if-let patterns to plain let destructures - dataflow.rs: remove never-read callee fields from CallReturn/Destructured - incremental.rs: remove never-read lang field from CacheEntry cargo check and cargo clippy both clean after these changes.
Adds .github/workflows/perf-canary.yml — a path-filtered workflow that fires on PRs touching src/extractors/, src/domain/graph/, or crates/** and runs only the incremental-benchmark suite (full build + no-op + 1-file rebuild, both engines). Catches the class of regressions that accumulated invisibly across the Phase 8.x PRs and were only detected at v3.12.0 publish time. The regression guard gains BENCH_CANARY=1 mode: raises thresholds to 50%/100%/150% (standard/noisy/WASM) and skips the build, query, and resolution suites — only incremental checks run. This absorbs shared- runner timing variance while still blocking catastrophic regressions (+98% full build, +1827% 1-file rebuild from v3.12.0). Closes #1433
|
Claude finished @carlos-alm's task —— View job PR Review: ci(bench): add per-PR perf canary for extractor/graph/native changes
Root Cause AnalysisProblem Being Solved: Performance regressions in the extractor/native layers accumulated undetected across the Phase 8.x PR series and were only caught at v3.12.0 publish time (+98% native full build, +1827% native 1-file rebuild). The full pre-publish benchmark suite (20–60 min) is too slow to gate individual PRs. This PR adds a lightweight, path-filtered canary that runs only the incremental benchmark tier (5–10 min) on PRs that touch the relevant layers. Why This Approach: Targeted path filters + reduced benchmark scope is the correct design — run expensive checks only where regressions are most likely, keep the signal tight. The Risk Assessment: Low. Thresholds are raised, not lowered — this is explicitly a coarser gate that accepts more CI-noise in exchange for fast feedback, not a mechanism to hide real regressions. The safety argument (native engine catches real regressions; WASM only needs gross-catastrophe detection) is correctly stated in the comments. Issues Found1.
|
Greptile SummaryAdds a lightweight per-PR performance canary workflow (
Confidence Score: 5/5Safe to merge — adds a new workflow and expands a test file with no changes to production code paths. Both changed files are additive: a new workflow that only runs benchmarks and a test file extension that gates on an environment variable. The threshold values and skip logic are well-documented, internally consistent, and validated by guard tests that prevent silent no-op passes. Action versions and workflow conventions are consistent with the rest of the repo. Previous review feedback has been addressed. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
PR[Pull Request push] --> PF{Path filter matches?\nsrc/extractors/**\nsrc/domain/graph/**\ncrates/**\nscripts/benchmark*.ts\ntests/benchmarks/regression-guard.test.ts}
PF -->|No| SKIP[Workflow skipped]
PF -->|Yes| CHECKOUT[checkout + setup Node 22 + Rust]
CHECKOUT --> BUILD_NATIVE[napi build --release\ncrates/codegraph-core]
BUILD_NATIVE --> NPM[npm install with retry loop]
NPM --> CI_NATIVE[ci-install-native.mjs\noverlay local addon over published binary]
CI_NATIVE --> TS_BUILD[npm run build\nbuild TypeScript dist/]
TS_BUILD --> BENCH[incremental-benchmark.ts --version dev --dist\n→ incremental-canary-result.json]
BENCH --> UPDATE[update-incremental-report.ts\nmutates INCREMENTAL-BENCHMARKS.md locally]
UPDATE --> GUARD{npm run test:regression-guard\nRUN_REGRESSION_GUARD=1\nBENCH_CANARY=1}
GUARD -->|native Full build >50%\nor 1-file rebuild >100%| FAIL[❌ Job fails — regression detected]
GUARD -->|WASM timing >150%| FAIL
GUARD -->|All within thresholds| PASS[✅ Job passes]
PASS --> UPLOAD[Upload incremental-canary-result.json artifact]
FAIL --> UPLOAD
Reviews (4): Last reviewed commit: "fix: resolve merge conflicts with main" | Re-trigger Greptile |
| concurrency: | ||
| group: perf-canary-${{ github.ref }} | ||
| cancel-in-progress: true |
There was a problem hiding this comment.
The workflow is missing an explicit
permissions: block. Other workflows in this repo (e.g. benchmark.yml) use permissions: {} to lock down the token scope. Without an explicit declaration the job inherits the repo default, which may be broader than needed for a read-only benchmark run that only writes an artifact.
| concurrency: | |
| group: perf-canary-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: {} | |
| concurrency: | |
| group: perf-canary-${{ github.ref }} | |
| cancel-in-progress: true |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed — added permissions: {} at workflow level (above concurrency:) to lock down the token scope, consistent with benchmark.yml and publish.yml.
| paths: | ||
| - "src/extractors/**" | ||
| - "src/domain/graph/**" | ||
| - "crates/**" | ||
| - "scripts/benchmark.ts" | ||
| - "scripts/incremental-benchmark.ts" | ||
| - "scripts/lib/bench-config.ts" | ||
| - "scripts/lib/fork-engine.ts" |
There was a problem hiding this comment.
The path filter doesn't include
tests/benchmarks/regression-guard.test.ts or scripts/update-incremental-report.ts. This very PR modifies the former to introduce BENCH_CANARY mode, but the canary won't fire for that change because no src/extractors/** / crates/** paths were touched. A future PR that adjusts the canary thresholds or the report-update logic will silently bypass this gate: the full pre-publish-benchmark runs but without BENCH_CANARY=1, so the BENCH_CANARY code path is never exercised in CI.
| paths: | |
| - "src/extractors/**" | |
| - "src/domain/graph/**" | |
| - "crates/**" | |
| - "scripts/benchmark.ts" | |
| - "scripts/incremental-benchmark.ts" | |
| - "scripts/lib/bench-config.ts" | |
| - "scripts/lib/fork-engine.ts" | |
| paths: | |
| - "src/extractors/**" | |
| - "src/domain/graph/**" | |
| - "crates/**" | |
| - "scripts/benchmark.ts" | |
| - "scripts/incremental-benchmark.ts" | |
| - "scripts/lib/bench-config.ts" | |
| - "scripts/lib/fork-engine.ts" | |
| - "scripts/update-incremental-report.ts" | |
| - "tests/benchmarks/regression-guard.test.ts" |
There was a problem hiding this comment.
Fixed — added both scripts/update-incremental-report.ts and tests/benchmarks/regression-guard.test.ts to the path filter. PRs that only touch the canary infrastructure will now correctly trigger the canary run.
| - name: Regression guard (50% threshold) | ||
| env: | ||
| RUN_REGRESSION_GUARD: "1" | ||
| BENCH_CANARY: "1" | ||
| run: npm run test:regression-guard |
There was a problem hiding this comment.
Canary must be a required status check to act as a gate
The workflow is a separate file and is not included in ci.yml's ci-pipeline aggregator (needs: [lint, native-host-build, test, ...]). For it to actually block merges on regressing PRs, perf-canary / Perf canary (incremental tiers) needs to be added as a required status check in branch protection rules. Without that step the canary is advisory only — a failing run won't prevent the PR from being merged.
There was a problem hiding this comment.
Acknowledged — making the canary a required status check in branch protection rules is a repo settings change, not a code change. Filed as a follow-up action for the repo admin to add perf-canary / Perf canary (incremental tiers) to required status checks after this PR merges.
… excess fetch-depth
- Add `permissions: {}` to lock down token scope for the read-only canary
- Add `scripts/update-incremental-report.ts` and
`tests/benchmarks/regression-guard.test.ts` to path filter so PRs that
modify the canary machinery itself also trigger the canary
- Remove `fetch-depth: 0` (full history not needed; canary compares against
committed benchmark data, not git refs)
- Align `node-version: 22` with the integer format used in ci.yml
|
Addressed all review feedback:
|
Summary
.github/workflows/perf-canary.yml— a path-filtered workflow that fires only on PRs touchingsrc/extractors/**,src/domain/graph/**,crates/**, or the benchmark scripts themselvesBENCH_CANARY=1mode in the regression guardPrevents the class of regressions that accumulated invisibly across the Phase 8.x PRs and were only caught at v3.12.0 publish time (+98% native full build, +1827% native 1-file rebuild). The canary would have caught both within 10 minutes of the first regressing PR landing.
Changes:
tests/benchmarks/regression-guard.test.ts— addsBENCH_CANARYmode:.github/workflows/perf-canary.yml— new workflow:pull_requestwith path filters for extractors/graph/cratesci.yml)incremental-benchmark.ts --dist, updates the incremental report, then invokes the regression guard withBENCH_CANARY=1Closes #1433